// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2021 Huawei Technologies Co., Ltd */

#define pr_fmt(fmt) KBUILD_MODNAME ": [XSP]" fmt

#include <linux/inetdevice.h>
#include <linux/netdevice.h>
#include <net/xfrm.h>
#include <net/esp.h>
#include <crypto/aead.h>
#include "ossl_knl.h"

#ifdef CONFIG_XFRM_OFFLOAD
#ifndef _LLT_TEST_
#include <linux/hashtable.h>
#endif

#include "hinic3_crm.h"
#include "hinic3_nic_dev.h"
#include "hinic3_xfrm.h"
#include "hisec_cfg.h"
#include "hisec_alg.h"
#include "hisec_common.h"
#include "hinic3_ipsec.h"
#include "hinic3_xsp.h"

#define HINIC3_SP_KEY_MATCH(sp_key, tmp_sp, ipv4) \
	((sp_key)->proto == (tmp_sp)->xp->selector.proto && \
	 ((((ipv4) && \
	    (sp_key)->saddr.a4 == (tmp_sp)->xp->selector.saddr.a4) && \
	   ((ipv4) && \
	    (sp_key)->daddr.a4 == (tmp_sp)->xp->selector.daddr.a4)) || \
	  ((!(ipv4) && !memcmp(&(sp_key)->saddr.a6, \
			       &(tmp_sp)->xp->selector.saddr.a6, \
			       sizeof((tmp_sp)->xp->selector.saddr.a6))) && \
	   (!(ipv4) && !memcmp(&(sp_key)->daddr.a6, \
			       &(tmp_sp)->xp->selector.daddr.a6, \
			       sizeof((tmp_sp)->xp->selector.daddr.a6))))))

static struct hinic3_xfrm_sp *
hinic3_alloc_xfrm_sp(struct xfrm_policy *xp)
{
	struct hinic3_xfrm_sp *sp = NULL;
	struct xfrm_tmpl *xt = NULL;

	sp = kzalloc(sizeof(*sp), GFP_KERNEL);
	if (!sp)
		return NULL;

	xt = &xp->xfrm_vec[0];
	sp->xp = xp;
	sp->spi = ntohl(xt->id.spi);
	sp->ipsec_proto = xt->id.proto;

	return sp;
}

static void hinic3_free_xfrm_sp(struct hinic3_xfrm_sp *sp)
{
	kfree(sp->xp);
	kfree(sp);
}

static inline u32 hinic3_gen_sp_hash_key(struct hinic3_xfrm_sp_key *sp_key)
{
	return jhash(sp_key, sizeof(*sp_key), 0);
}

static void hinic3_make_sp_key(struct xfrm_policy *xp, struct hinic3_xfrm_sp_key *sp_key)
{
	memset(sp_key, 0, sizeof(*sp_key));

	/* note: xfrm state use network byte order is big endian */
	if (xp->family == AF_INET6) {
		memcpy(&sp_key->saddr.a6, &xp->selector.saddr.a6,
		       sizeof(xp->selector.saddr.a6));
		memcpy(&sp_key->daddr.a6, &xp->selector.daddr.a6,
		       sizeof(xp->selector.daddr.a6));
		/* show 128bit source IPv6 Address */
		pr_info("SP key, IPv6 saddr:0x%x-0x%x-0x%x-0x%x\n",
			ntohl(sp_key->saddr.a6[0]), ntohl(sp_key->saddr.a6[1]),
			ntohl(sp_key->saddr.a6[2]), ntohl(sp_key->saddr.a6[3]));
		/* show 128bit dest IPv6 Address */
		pr_info("SP key, IPv6 daddr:0x%x-0x%x-0x%x-0x%x\n",
			ntohl(sp_key->daddr.a6[0]), ntohl(sp_key->daddr.a6[1]),
			ntohl(sp_key->daddr.a6[2]), ntohl(sp_key->daddr.a6[3]));
	} else {
		sp_key->saddr.a4 = xp->selector.saddr.a4;
		sp_key->daddr.a4 = xp->selector.daddr.a4;
		pr_info("SP key, IPv4 saddr:0x%x\n", ntohl(sp_key->saddr.a4));
		pr_info("SP key, IPv4 daddr:0x%x\n", ntohl(sp_key->daddr.a4));
	}

	sp_key->proto = xp->selector.proto;

	pr_info("SP key, proto:%d\n", sp_key->proto);
}

struct hinic3_xfrm_sp *
hinic3_search_sp_by_key(struct hinic3_xfrm_sp_key *sp_key, bool ipv4)
{
	struct hinic3_xfrm_sp *tmp_sp = NULL;
	u32 hash_key = hinic3_gen_sp_hash_key(sp_key);

#ifndef _LLT_TEST_

	/* hash key always based on inbound direction */
	rcu_read_lock();
	hash_for_each_possible_rcu(g_hinic3_spd.spdb, tmp_sp, hlist, hash_key) {
		if (HINIC3_SP_KEY_MATCH(sp_key, tmp_sp, ipv4))
			break;
	}
	rcu_read_unlock();
#endif

	return tmp_sp;
}

static void hinic3_spdb_add_sp(struct hinic3_xfrm_sp *sp, struct hinic3_xfrm_sp_key *sp_key)
{
	unsigned long flags;
	u32 hash_key = hinic3_gen_sp_hash_key(sp_key);

	spin_lock_irqsave(&g_hinic3_spd.spdb_lock, flags);
	hash_add_rcu(g_hinic3_spd.spdb, &sp->hlist, hash_key);
	spin_unlock_irqrestore(&g_hinic3_spd.spdb_lock, flags);
}

static void hinic3_spdb_del_sp(struct hinic3_xfrm_sp *sp)
{
	unsigned long flags;

	spin_lock_irqsave(&g_hinic3_spd.spdb_lock, flags);
	hash_del_rcu(&sp->hlist);
	spin_unlock_irqrestore(&g_hinic3_spd.spdb_lock, flags);
}

int hinic3_add_xfrm_dev_sp(struct xfrm_policy *xp)
{
	struct hinic3_xfrm_sp *new_sp = NULL;
	struct hinic3_xfrm_sp_key sp_key;
	int ret = 0;

	hinic3_make_sp_key(xp, &sp_key);
	new_sp = hinic3_search_sp_by_key(&sp_key,
					 (xp->family == AF_INET) ?
					true : false);
	if (new_sp) {
		pr_info("SP item existed: %d\n", ret);
		return ret;
	}

	new_sp = hinic3_alloc_xfrm_sp(xp);
	if (!new_sp) {
		ret = -ENOMEM;
		pr_err("Alloc xfrm sp failed: %d\n", ret);
		goto alloc_sp_fail;
	}

	hinic3_spdb_add_sp(new_sp, &sp_key);

	return ret;

alloc_sp_fail:
	return ret;
}
EXPORT_SYMBOL(hinic3_add_xfrm_dev_sp);

void hinic3_del_xfrm_dev_sp(struct xfrm_policy *xp)
{
	struct hinic3_xfrm_sp *sp = NULL;
	struct hinic3_xfrm_sp_key sp_key;

	hinic3_make_sp_key(xp, &sp_key);
	sp = hinic3_search_sp_by_key(&sp_key,
				     (xp->family == AF_INET) ? true : false);
	if (!sp) {
		pr_err("Can not search sp by key\n");
		return;
	}

	hinic3_spdb_del_sp(sp);
	hinic3_free_xfrm_sp(sp);
}
EXPORT_SYMBOL(hinic3_del_xfrm_dev_sp);

#endif

